package com.wzdigit.wms.basic.service.code;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.wzdigit.mes.basic.api.BasicDataItemDetailApi;
import com.wzdigit.mes.basic.api.BasicGetCustomerApi;
import com.wzdigit.mes.basic.api.GetSerialNumberApi;
import com.wzdigit.mes.basic.dto.DataItemDetailDTO;
import com.wzdigit.wms.basic.domain.asn.AsnDtl;
import com.wzdigit.wms.basic.domain.asn.AsnHeader;
import com.wzdigit.wms.basic.domain.basic.Sku;
import com.wzdigit.wms.basic.domain.basic.SkuCtrl;
import com.wzdigit.wms.basic.domain.dtos.inventory.CreateLotnoDto;
import com.wzdigit.wms.basic.domain.inv.SealDtl;
import com.wzdigit.wms.basic.domain.inv.SealHeader;
import com.wzdigit.wms.basic.domain.inventory.Goods;
import com.wzdigit.wms.basic.domain.inventory.LotAttrDtl;
import com.wzdigit.wms.basic.domain.inventory.Lotno;
import com.wzdigit.wms.basic.domain.mid.IfTransferDtl;
import com.wzdigit.wms.basic.domain.transfer.GdnDtl;
import com.wzdigit.wms.basic.domain.transfer.GdnHeader;
import com.wzdigit.wms.basic.domain.transfer.TransferDtl;
import com.wzdigit.wms.basic.enums.AsnTypeEnum;
import com.wzdigit.wms.basic.enums.BaseIsEnabledEnum;
import com.wzdigit.wms.basic.enums.BaseIsKeyEnum;
import com.wzdigit.wms.basic.enums.basic.*;
import com.wzdigit.wms.basic.mapper.core.basic.LotAttrDtlMapper;
import com.wzdigit.wms.basic.mapper.core.basic.SkuCtrlMapper;
import com.wzdigit.wms.basic.mapper.core.basic.SkuMapper;
import com.wzdigit.wms.basic.mapper.core.basic.SystemMapper;
import com.wzdigit.wms.basic.mapper.core.inv.SealDtlMapper;
import com.wzdigit.wms.basic.mapper.core.inv.SealHeaderMapper;
import com.wzdigit.wms.basic.mapper.core.inventory.GoodsMapper;
import com.wzdigit.wms.basic.mapper.core.inventory.LotnoMapper;
import com.wzdigit.wms.basic.mapper.core.mid.IfTransferDtlMapper;
import com.wzdigit.wms.basic.mapper.core.order.AsnDtlMapper;
import com.wzdigit.wms.basic.mapper.core.order.AsnHeaderMapper;
import com.wzdigit.wms.basic.mapper.core.transfer.GdnDtlMapper;
import com.wzdigit.wms.basic.mapper.core.transfer.GdnHeaderMapper;
import com.wzdigit.wms.basic.mapper.core.transfer.TransferDtlMapper;
import com.wzdigit.wms.basic.util.StringUtils;
import com.wzdigit.wms.basic.util.LotnoUtils;
import com.wzdigit.wms.basic.util.common.CommonString;
import com.wzdigit.wms.basic.util.common.LockNumber;

import lombok.extern.slf4j.Slf4j;
import net.dreamlu.mica.core.utils.BeanUtil;
import org.apache.dubbo.config.annotation.Reference;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 批次号 服务类
 *
 * @author yangdongjie
 * @ZoneAttrService.java
 * @2021年1月4日上午11:16:31
 */
@Service
@Slf4j
public class LotnoService {

    @Autowired
    private LotnoMapper lotnoMapper;

    @Autowired
    private GoodsMapper goodsMapper;

    @Autowired
    private SkuCtrlMapper skuCtrlMapper;

    @Autowired
    private SkuMapper skuMapper;

    @Autowired
    private IfTransferDtlMapper ifTransferDtlMapper;

    @Autowired
    private TransferDtlMapper transferDtlMapper;

    @Autowired
    private LotAttrDtlMapper lotAttrDtlMapper;

    @Autowired
    private GdnHeaderMapper gdnHeaderMapper;

    @Autowired
    private GdnDtlMapper gdnDtlMapper;

    @Autowired
    private AsnHeaderMapper asnHeaderMapper;

    @Autowired
    private AsnDtlMapper asnDtlMapper;
    
    @Autowired
    private SealDtlMapper sealDtlMapper;
    
    @Autowired
    private SealHeaderMapper sealHeaderMapper;

    @Autowired
    private SystemMapper systemMapper;
    
    @Autowired
    private RedissonClient redisson;

    @Reference(version = "${dubbo.consumers.basic.SerialNumber.api.version:1.0.0}", check = false, timeout = 300000)
    private GetSerialNumberApi getSerialNumberApi;

    @Reference(version = "${dubbo.consumers.basic.data.item.detail.api:1.0.0}", check = false, timeout = 300000)
    private BasicDataItemDetailApi dataItemDetailApi;

    @Reference(version = "${dubbo.consumers.basic.customer.api:1.0.0}", check = false, timeout = 300000)
    private BasicGetCustomerApi customerApi;

    @Value(value = "${default_owner.code}")
    private String DEFAULT_OWNER;

    @Value(value = "${default_owner.name}")
    private String DEFAULT_OWNER_NAME;

    private static final SimpleDateFormat FORMAT = new SimpleDateFormat("yyyyMMdd");

    private static final String WMS_GOODS = "WMS_GOODS";
    private static final String WMS_GDN_DTL = "WMS_GDN_DTL";
    private static final String WMS_ASN_DTL = "WMS_ASN_DTL";
    private static final String WMS_SEAL_DTL = "WMS_SEAL_DTL";
    private static final String WMS_TRANSFER_DTL = "WMS_TRANSFER_DTL";
    private static final String WMS_IF_TRANSFER_DTL = "WMS_IF_TRANSFER_DTL";
    
    private static final boolean UPDATE_LOT_NOKEYATTR = true;		// 需要更新非关键属性值标识
    private static final boolean NOT_UPDATE_LOT_NOKEYATTR = false;	// 不需要更新非关键属性值标识
    
    // 已启用状态
    private Integer IS_ENABLED_USE_CODE = BaseIsEnabledEnum.USE.code;
    // 默认用户
    private String LOGIN_NAME = CommonString.DEFAULT_USER_NAME;
    // 默认货主
    private Integer CUSTOMER_ID;
    
    /**
     * 根据货品ID获取批次属性
     * 
     * @param skuId
     * @return
     * @throws Exception 
     */
    public List<LotAttrDtl> selectLotAttrDtlBySkuId(Integer skuId) throws Exception {
    	// 货品控制
        QueryWrapper<SkuCtrl> skuCtrlQueryWrapper = new QueryWrapper<>();
        skuCtrlQueryWrapper.eq("SKU_ID", skuId);
        SkuCtrl skuCtrl = skuCtrlMapper.selectOne(skuCtrlQueryWrapper);
        if(skuCtrl == null || skuCtrl.getLotAttrId() == null) {
        	throw new Exception("物料批次属性为空！");
        }
        // 属性明细（1-12排序）
        QueryWrapper<LotAttrDtl> lotAttrDtlQueryWrapper = new QueryWrapper<>();
        lotAttrDtlQueryWrapper.eq("LOT_ATTR_ID", skuCtrl.getLotAttrId());
        lotAttrDtlQueryWrapper.eq("IS_ENABLED", IS_ENABLED_USE_CODE);
        lotAttrDtlQueryWrapper.orderByAsc("ATTR_SEQ");
        List<LotAttrDtl> lotAttrDtls = lotAttrDtlMapper.selectList(lotAttrDtlQueryWrapper);
        if(lotAttrDtls.size() == 0) {
        	throw new Exception("未找到已启用的批次属性！");
        }
		return lotAttrDtls;
    }

    /**
     * 根据货品ID获取批次属性 公司间交货
     *
     * @param skuId
     * @return
     * @throws Exception
     */
    public List<LotAttrDtl> selectLotAttrDtlBySkuIdCompaney(Integer skuId) throws Exception {
        // 货品控制
        QueryWrapper<SkuCtrl> skuCtrlQueryWrapper = new QueryWrapper<>();
        skuCtrlQueryWrapper.eq("SKU_ID", skuId);
        SkuCtrl skuCtrl = skuCtrlMapper.selectOne(skuCtrlQueryWrapper);
        if(skuCtrl == null || skuCtrl.getLotAttrId() == null) {
            throw new Exception("物料批次属性为空！");
        }
        // 属性明细（1-12排序）
        QueryWrapper<LotAttrDtl> lotAttrDtlQueryWrapper = new QueryWrapper<>();
        lotAttrDtlQueryWrapper.eq("LOT_ATTR_ID", skuCtrl.getLotAttrId());
        lotAttrDtlQueryWrapper.eq("IS_ENABLED", IS_ENABLED_USE_CODE);
        lotAttrDtlQueryWrapper.orderByAsc("ATTR_SEQ");
        List<LotAttrDtl> lotAttrDtls = lotAttrDtlMapper.selectList(lotAttrDtlQueryWrapper);
        if(lotAttrDtls.size() == 0) {
            throw new Exception("未找到已启用的批次属性！");
        }
        //公司间交货批次号生成规则
        for(LotAttrDtl item : lotAttrDtls){
            if("ASN_REF1".equals(item.getAttrCode())){
                item.setAttrCode("PO_NO");
            }
        }
        return lotAttrDtls;
    }

    /**
     * 根据批次号获取实体
     *
     * @param lotno
     * @return
     */
    public Lotno selectByLotno(String lotno) {
        QueryWrapper<Lotno> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("LOTNO", lotno);
        return lotnoMapper.selectOne(queryWrapper);
    }

    /**
     * 统计批次属性生成了多少批次号
     * 
     * @param lotAttrId
     * @return
     */
    public int selectCountByLotAttrId(Integer lotAttrId) {
        QueryWrapper<Lotno> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("LOT_ATTR_ID", lotAttrId);
        Integer count = lotnoMapper.selectCount(queryWrapper);
        return count == null ? 0 : count;
    }

    /**
     * 根据SN查找/生成批次号（注：需要更新非关键属性）
     * 
     * @param sn
     * @param loginName
     * @return
     * @throws Exception
     */
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public Lotno createLotnoBySn(String sn, String loginName) throws Exception {
    	try {
	        // 货品清单
	        QueryWrapper<Goods> queryWrapper = new QueryWrapper<>();
	        queryWrapper.eq("GOODS_SN", sn);
	        Goods goods = goodsMapper.selectOne(queryWrapper);
            return createLotnoByGoods(goods, loginName);
        } catch (Exception e) {
            throw e;
        }
    }

    /**
     * 根据goods查找/生成批次号（注：需要更新非关键属性）
     * 
     * @param goods
     * @param loginName
     * @return
     * @throws Exception
     */
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public Lotno createLotnoByGoods(Goods goods, String loginName) throws Exception {
        try {
        	Integer skuId = goods.getSkuId();
        	Integer owner = goods.getOwner();
        	List<LotAttrDtl> lotAttrDtls = selectLotAttrDtlBySkuId(skuId);
        	Integer lotAttrId = lotAttrDtls.get(0).getLotAttrId();

            // 入参DTO
            CreateLotnoDto dto = new CreateLotnoDto();
            Class<? extends CreateLotnoDto> dtoClass = dto.getClass();

            // 货品清单CLASS
            Class<? extends Goods> goodsClass = goods.getClass();

            // 属性MAP（属性名称）
            Map<String, Object> lotAttr = new LinkedHashMap<String, Object>(lotAttrDtls.size());
            // 属性MAP（属性编码）
            Map<String, Object> lotAttrCode = new LinkedHashMap<String, Object>(lotAttrDtls.size());

            // 关键属性位置
            Set<Integer> keyAttrSeqSet = new HashSet<>();
            // 非关键属性位置
            Set<Integer> noKeyAttrSeqSet = new HashSet<>();
            
            // 遍历设置属性明细
            for (LotAttrDtl dtl : lotAttrDtls) {
                String lotAttrValue = "";    // 转换后的货品清单数值

                // 判断属性code是不是【WMS_GOODS】表的字段
                if (systemMapper.existsColumn(WMS_GOODS, dtl.getAttrCode().toUpperCase())) {
                    // 属性code转换驼峰格式，获取货品清单对应字段的数值
                    String fieldName = StringUtils.strTransformation(dtl.getAttrCode());
                    Field goodsField = goodsClass.getDeclaredField(fieldName);
                    goodsField.setAccessible(true);

                    // 转换数值格式
                    Object fieldValue = goodsField.get(goods);    // 原货品清单数值
                    if (fieldValue != null && StringUtils.isNotNull(fieldValue.toString())) {
                        if (fieldValue instanceof Date) {
                            lotAttrValue = FORMAT.format(fieldValue);
                        } else {
                            lotAttrValue = fieldValue.toString();
                        }

                        // 获取入参dto对应属性的set函数，进行填值
                        Method dtoMethod = dtoClass.getMethod("setLotAttr" + dtl.getAttrSeq(), String.class);
                        dtoMethod.setAccessible(true);
                        dtoMethod.invoke(dto, lotAttrValue);
                    }
                }
                // PUT属性JSON（key:value）
                lotAttr.put(dtl.getAttr(), lotAttrValue);
                lotAttrCode.put(dtl.getAttrCode(), lotAttrValue);
                
                // 判断是否关键属性
                if(Objects.equals(BaseIsKeyEnum.YES.code, dtl.getIsKeyAttr())) {
                	keyAttrSeqSet.add(dtl.getAttrSeq());
            	} else {
            		noKeyAttrSeqSet.add(dtl.getAttrSeq());
            	}
            }
            // 以json格式，key:value形式存储批次属性1-12的内容
            dto.setLotAttr(JSON.toJSONString(lotAttr));
            dto.setLotAttrCode(JSON.toJSONString(lotAttrCode));

            // 批次属性ID
            dto.setLotAttrId(lotAttrId);
            // 货品
            dto.setSkuId(skuId);
            // 货主
            dto.setCustomerId(owner);
            return createLotno(dto, keyAttrSeqSet, noKeyAttrSeqSet, lotAttrDtls, UPDATE_LOT_NOKEYATTR);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 根据【发货单明细】查找批次号
     *
     * @param gdnDtlId
     * @return
     */
    public Lotno getLotnoByGdnDtlId(Long gdnDtlId) {
        try {
            // 发货单明细
            GdnDtl gdnDtl = gdnDtlMapper.selectById(gdnDtlId);

            // 发货单主档
            GdnHeader gdnHeader = gdnHeaderMapper.selectById(gdnDtl.getGdnHeaderId());

            Integer skuId = gdnDtl.getSkuId();
        	Integer owner = gdnHeader.getOwner();
        	List<LotAttrDtl> lotAttrDtls = selectLotAttrDtlBySkuId(skuId);
        	Integer lotAttrId = lotAttrDtls.get(0).getLotAttrId();

            // 入参DTO
            CreateLotnoDto dto = new CreateLotnoDto();
            Class<? extends CreateLotnoDto> dtoClass = dto.getClass();

            // 发货单明细CLASS
            Class<? extends GdnDtl> gdnDtlClass = gdnDtl.getClass();

            // 属性MAP（属性名称）
            Map<String, Object> lotAttr = new LinkedHashMap<String, Object>(lotAttrDtls.size());
            // 属性MAP（属性编码）
            Map<String, Object> lotAttrCode = new LinkedHashMap<String, Object>(lotAttrDtls.size());

            // 关键属性位置
            Set<Integer> keyAttrSeqSet = new HashSet<>();
            // 非关键属性位置
            Set<Integer> noKeyAttrSeqSet = new HashSet<>();
            
            // 遍历设置属性明细
            for (LotAttrDtl dtl : lotAttrDtls) {
                // 转换后的数值
                String lotAttrValue = "";

                String fieldName = null;
                // 转大写
                String attrCode = dtl.getAttrCode().toUpperCase();
                // 判断是不是发货单明细表的字段
                if (systemMapper.existsColumn(WMS_GDN_DTL, attrCode)) {
                    fieldName = attrCode;
                } else {
                    GdnLotAttrDtlEnum gdnLotAttrDtlEnum = GdnLotAttrDtlEnum.getEnumByCode(attrCode);
                    if (gdnLotAttrDtlEnum != null) {
                        fieldName = gdnLotAttrDtlEnum.getEnumName();
                    }
                }
                if (fieldName != null) {
                    // 转驼峰格式
                    fieldName = StringUtils.strTransformation(fieldName);

                    // 属性code转换驼峰格式，获取货品清单对应字段的数值
                    Field gdnDtlField = gdnDtlClass.getDeclaredField(fieldName);
                    gdnDtlField.setAccessible(true);

                    // 转换数值格式
                    Object fieldValue = gdnDtlField.get(gdnDtl);    // 原发货单明细数值
                    if (fieldValue != null && StringUtils.isNotNull(fieldValue.toString())) {
                        if (fieldValue instanceof Date) {
                            lotAttrValue = FORMAT.format(fieldValue);
                        } else {
                            lotAttrValue = fieldValue.toString();
                        }

                        // 获取入参dto对应属性的set函数，进行填值
                        Method dtoMethod = dtoClass.getMethod("setLotAttr" + dtl.getAttrSeq(), String.class);
                        dtoMethod.setAccessible(true);
                        dtoMethod.invoke(dto, lotAttrValue);
                    }
                }
                // PUT属性JSON（key:value）
                lotAttr.put(dtl.getAttr(), lotAttrValue);
                lotAttrCode.put(dtl.getAttrCode(), lotAttrValue);
                
                // 判断是否关键属性
                if(Objects.equals(BaseIsKeyEnum.YES.code, dtl.getIsKeyAttr())) {
                	keyAttrSeqSet.add(dtl.getAttrSeq());
            	} else {
            		noKeyAttrSeqSet.add(dtl.getAttrSeq());
            	}
            }
            // 以json格式，key:value形式存储批次属性1-12的内容
            dto.setLotAttr(JSON.toJSONString(lotAttr));
            dto.setLotAttrCode(JSON.toJSONString(lotAttrCode));

            // 批次属性ID
            dto.setLotAttrId(lotAttrId);
            // 货品
            dto.setSkuId(skuId);
            // 货主
            dto.setCustomerId(owner);
            return createLotno(dto, keyAttrSeqSet, noKeyAttrSeqSet, lotAttrDtls, NOT_UPDATE_LOT_NOKEYATTR);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 根据【收货单明细】查找批次号
     *
     * @param asnDtlId
     * @return
     */
    public Lotno getLotnoByAsnDtlId(Integer asnDtlId) {
        try {
            // 收货单明细
            AsnDtl asnDtl = asnDtlMapper.selectById(asnDtlId);

            // 收货单主档
            AsnHeader asnHeader = asnHeaderMapper.selectById(asnDtl.getAsnHeaderId());

            Integer skuId = asnDtl.getSkuId();
        	Integer owner = asnHeader.getOwner();
            List<LotAttrDtl> lotAttrDtls = null;
        	if(AsnTypeEnum.R09.code.equals(asnHeader.getAsnType())||AsnTypeEnum.R10.code.equals(asnHeader.getAsnType())){
                lotAttrDtls = selectLotAttrDtlBySkuIdCompaney(skuId);
            }else{
                lotAttrDtls= selectLotAttrDtlBySkuId(skuId);
            }

        	Integer lotAttrId = lotAttrDtls.get(0).getLotAttrId();

            // 入参DTO
            CreateLotnoDto dto = new CreateLotnoDto();
            Class<? extends CreateLotnoDto> dtoClass = dto.getClass();

            // 收货单明细CLASS
            Class<? extends AsnDtl> asnDtlClass = asnDtl.getClass();

            // 属性MAP（属性名称）
            Map<String, Object> lotAttr = new LinkedHashMap<String, Object>(lotAttrDtls.size());
            // 属性MAP（属性编码）
            Map<String, Object> lotAttrCode = new LinkedHashMap<String, Object>(lotAttrDtls.size());

            // 关键属性位置
            Set<Integer> keyAttrSeqSet = new HashSet<>();
            // 非关键属性位置
            Set<Integer> noKeyAttrSeqSet = new HashSet<>();
            
            // 遍历设置属性明细
            for (LotAttrDtl dtl : lotAttrDtls) {
                // 转换后的数值
                String lotAttrValue = "";

                String fieldName = null;
                // 转大写
                String attrCode = dtl.getAttrCode().toUpperCase();
                // 判断是不是收货单明细表的字段
                if (systemMapper.existsColumn(WMS_ASN_DTL, attrCode)) {
                    fieldName = attrCode;
                } else {
                    AsnLotAttrDtlEnum asnLotAttrDtlEnum = AsnLotAttrDtlEnum.getEnumByCode(attrCode);
                    if (asnLotAttrDtlEnum != null) {
                        fieldName = asnLotAttrDtlEnum.getEnumName();
                    }
                }
                if (fieldName != null) {
                    // 转驼峰格式
                    fieldName = StringUtils.strTransformation(fieldName);

                    // 属性code转换驼峰格式，获取货品清单对应字段的数值
                    Field asnDtlField = asnDtlClass.getDeclaredField(fieldName);
                    asnDtlField.setAccessible(true);

                    // 转换数值格式
                    Object fieldValue = asnDtlField.get(asnDtl);    // 原收货单明细数值
                    if (fieldValue != null && StringUtils.isNotNull(fieldValue.toString())) {
                        if (fieldValue instanceof Date) {
                            lotAttrValue = FORMAT.format(fieldValue);
                        } else {
                            lotAttrValue = fieldValue.toString();
                        }

                        // 获取入参dto对应属性的set函数，进行填值
                        Method dtoMethod = dtoClass.getMethod("setLotAttr" + dtl.getAttrSeq(), String.class);
                        dtoMethod.setAccessible(true);
                        dtoMethod.invoke(dto, lotAttrValue);
                    }
                }
                // PUT属性JSON（key:value）

                lotAttr.put(dtl.getAttr(), lotAttrValue);
                //处理特殊类型
                if("ASN_REF1".equals(dtl.getAttr())){
                    lotAttrCode.put("PO_NO", lotAttrValue);
                }else{
                    lotAttrCode.put(dtl.getAttrCode(), lotAttrValue);
                }


                // 判断是否关键属性
                if(Objects.equals(BaseIsKeyEnum.YES.code, dtl.getIsKeyAttr())) {
                	keyAttrSeqSet.add(dtl.getAttrSeq());
            	} else {
            		noKeyAttrSeqSet.add(dtl.getAttrSeq());
            	}
            }
            // 以json格式，key:value形式存储批次属性1-12的内容
            dto.setLotAttr(JSON.toJSONString(lotAttr));
            dto.setLotAttrCode(JSON.toJSONString(lotAttrCode));

            // 批次属性ID
            dto.setLotAttrId(lotAttrId);
            // 货品
            dto.setSkuId(skuId);
            // 货主
            dto.setCustomerId(owner);
            return createLotno(dto, keyAttrSeqSet, noKeyAttrSeqSet, lotAttrDtls, NOT_UPDATE_LOT_NOKEYATTR);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


    /**
     * 根据【调拨单明细】查找批次号
     * @param transferDtlId
     * @param type：0=来源、1=目标
     * @return
     */
    /**
     * @param transferDtlId
     * @return
     */
    public Lotno getLotnoByTransferDtlId(Long transferDtlId, Integer type) {
        try {
            // 调拨单明细
            String prefix = null;
            TransferDtl transferDtl = transferDtlMapper.selectById(transferDtlId);
            if (type == 0) {    // 来源
                prefix = "FM_";
            } else if (type == 1) {    // 目的
                prefix = "TO_";
            } else {
                return null;
            }

            Integer skuId = type == 0 ? transferDtl.getFmSku() : transferDtl.getToSku();
        	Integer owner = type == 0 ? transferDtl.getFmOwner() : transferDtl.getToOwner();
        	List<LotAttrDtl> lotAttrDtls = selectLotAttrDtlBySkuId(skuId);
        	Integer lotAttrId = lotAttrDtls.get(0).getLotAttrId();
        	
            // 入参DTO
            CreateLotnoDto dto = new CreateLotnoDto();
            Class<? extends CreateLotnoDto> dtoClass = dto.getClass();

            // 调拨单单明细CLASS
            Class<? extends TransferDtl> transferDtlClass = transferDtl.getClass();

            // 属性MAP（属性名称）
            Map<String, Object> lotAttr = new LinkedHashMap<String, Object>(lotAttrDtls.size());
            // 属性MAP（属性编码）
            Map<String, Object> lotAttrCode = new LinkedHashMap<String, Object>(lotAttrDtls.size());

            // 关键属性位置
            Set<Integer> keyAttrSeqSet = new HashSet<>();
            // 非关键属性位置
            Set<Integer> noKeyAttrSeqSet = new HashSet<>();
            
            // 遍历设置属性明细
            for (LotAttrDtl dtl : lotAttrDtls) {
                // 转换后的数值
                String lotAttrValue = "";

                String fieldName = null;
                // 转大写
                String attrCode = (prefix + dtl.getAttrCode()).toUpperCase();
                // 判断是不是发货单明细表的字段
                if (systemMapper.existsColumn(WMS_TRANSFER_DTL, attrCode)) {
                    fieldName = attrCode;
                } else {
                    TransferLotAttrDtlEnum transferLotAttrDtlEnum = TransferLotAttrDtlEnum.getEnumByCode(attrCode);
                    if (transferLotAttrDtlEnum != null) {
                        fieldName = transferLotAttrDtlEnum.getEnumName();
                    }
                }
                if (fieldName != null) {
                    // 转驼峰格式
                    fieldName = StringUtils.strTransformation(fieldName);

                    // 属性code转换驼峰格式，获取货品清单对应字段的数值
                    Field transferDtlField = transferDtlClass.getDeclaredField(fieldName);
                    transferDtlField.setAccessible(true);

                    // 转换数值格式
                    Object fieldValue = transferDtlField.get(transferDtl);    // 原调拨单单明细数值
                    if (fieldValue != null && StringUtils.isNotNull(fieldValue.toString())) {
                        if (fieldValue instanceof Date) {
                            lotAttrValue = FORMAT.format(fieldValue);
                        } else {
                            lotAttrValue = fieldValue.toString();
                        }
                        // 获取入参dto对应属性的set函数，进行填值
                        Method dtoMethod = dtoClass.getMethod("setLotAttr" + dtl.getAttrSeq(), String.class);
                        dtoMethod.setAccessible(true);
                        dtoMethod.invoke(dto, lotAttrValue);
                    }
                }
                // PUT属性JSON（key:value）
                lotAttr.put(dtl.getAttr(), lotAttrValue);
                lotAttrCode.put(dtl.getAttrCode(), lotAttrValue);

                // 判断是否关键属性
                if(Objects.equals(BaseIsKeyEnum.YES.code, dtl.getIsKeyAttr())) {
                	keyAttrSeqSet.add(dtl.getAttrSeq());
            	} else {
            		noKeyAttrSeqSet.add(dtl.getAttrSeq());
            	}
            }
            // 以json格式，key:value形式存储批次属性1-12的内容
            dto.setLotAttr(JSON.toJSONString(lotAttr));
            dto.setLotAttrCode(JSON.toJSONString(lotAttrCode));

            // 批次属性ID
            dto.setLotAttrId(lotAttrId);
            // 货品
            dto.setSkuId(skuId);
            // 货主
            dto.setCustomerId(owner);
            return createLotno(dto, keyAttrSeqSet, noKeyAttrSeqSet, lotAttrDtls, NOT_UPDATE_LOT_NOKEYATTR);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 根据【调拨中间表明细】查找批次号
     *
     * @param ifTransferDtlId
     * @param type：0=来源、1=目标
     * @return
     */
    public Lotno getLotnoByIfTransferDtlId(Integer ifTransferDtlId, Integer type) {
        try {
            String prefix = null;
            if (type == 0) {            // 来源
                prefix = "FROM_";
            } else if (type == 1) {     // 目的
                prefix = "TO_";
            } else {
                return null;
            }

            // 调拨中间表明细
            IfTransferDtl ifTransferDtl = ifTransferDtlMapper.selectById(ifTransferDtlId);

            // 获取货品
            QueryWrapper<Sku> skuQueryWrapper = new QueryWrapper<>();
            skuQueryWrapper.eq("SKU_CODE", ifTransferDtl.getMaterialCode());
            Sku sku = skuMapper.selectOne(skuQueryWrapper);

            Integer skuId = sku.getSkuId();
        	List<LotAttrDtl> lotAttrDtls = selectLotAttrDtlBySkuId(skuId);
        	Integer lotAttrId = lotAttrDtls.get(0).getLotAttrId();

            // 入参DTO
            CreateLotnoDto dto = new CreateLotnoDto();
            Class<? extends CreateLotnoDto> dtoClass = dto.getClass();

            // 调拨中间表明细CLASS
            Class<? extends IfTransferDtl> ifTransferDtlClass = ifTransferDtl.getClass();

            // 属性MAP（属性名称）
            Map<String, Object> lotAttr = new LinkedHashMap<String, Object>(lotAttrDtls.size());
            // 属性MAP（属性编码）
            Map<String, Object> lotAttrCode = new LinkedHashMap<String, Object>(lotAttrDtls.size());

            // 关键属性位置
            Set<Integer> keyAttrSeqSet = new HashSet<>();
            // 非关键属性位置
            Set<Integer> noKeyAttrSeqSet = new HashSet<>();
            
            // 遍历设置属性明细
            for (LotAttrDtl dtl : lotAttrDtls) {
                // 转换后的数值
                String lotAttrValue = "";

                String fieldName = null;
                // 转大写
                String attrCode = (prefix + dtl.getAttrCode()).toUpperCase();
                // 判断是不是发货单明细表的字段
                if (systemMapper.existsColumn(WMS_IF_TRANSFER_DTL, attrCode)) {
                    fieldName = attrCode;
                } else {
                    IfTransferLotAttrDtlEnum ifTransferLotAttrDtlEnum = IfTransferLotAttrDtlEnum.getEnumByCode(attrCode);
                    if (ifTransferLotAttrDtlEnum != null) {
                        fieldName = ifTransferLotAttrDtlEnum.getEnumName();
                    }
                }
                if (fieldName != null) {
                    // 转驼峰格式
                    fieldName = StringUtils.strTransformation(fieldName);

                    // 属性code转换驼峰格式，获取货品清单对应字段的数值
                    Field ifTransferDtlField = ifTransferDtlClass.getDeclaredField(fieldName);
                    ifTransferDtlField.setAccessible(true);

                    // 转换数值格式
                    Object fieldValue = ifTransferDtlField.get(ifTransferDtl);    // 原调拨中间表明细数值
                    if (fieldValue != null && StringUtils.isNotNull(fieldValue.toString())) {
                        if (fieldValue instanceof Date) {
                            lotAttrValue = FORMAT.format(fieldValue);
                        } else {
                            lotAttrValue = fieldValue.toString();
                        }

                        // 获取入参dto对应属性的set函数，进行填值
                        Method dtoMethod = dtoClass.getMethod("setLotAttr" + dtl.getAttrSeq(), String.class);
                        dtoMethod.setAccessible(true);
                        dtoMethod.invoke(dto, lotAttrValue);
                    }
                }
                // PUT属性JSON（key:value）
                lotAttr.put(dtl.getAttr(), lotAttrValue);
                lotAttrCode.put(dtl.getAttrCode(), lotAttrValue);

                // 判断是否关键属性
                if(Objects.equals(BaseIsKeyEnum.YES.code, dtl.getIsKeyAttr())) {
                	keyAttrSeqSet.add(dtl.getAttrSeq());
            	} else {
            		noKeyAttrSeqSet.add(dtl.getAttrSeq());
            	}
            }
            // 以json格式，key:value形式存储批次属性1-12的内容
            dto.setLotAttr(JSON.toJSONString(lotAttr));
            dto.setLotAttrCode(JSON.toJSONString(lotAttrCode));

            // 批次属性ID
            dto.setLotAttrId(lotAttrId);
            // 货品
            dto.setSkuId(skuId);
            // 货主
            dto.setCustomerId(null);
            return createLotno(dto, keyAttrSeqSet, noKeyAttrSeqSet, lotAttrDtls, NOT_UPDATE_LOT_NOKEYATTR);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    
    /**
     * 根据【封库或解封单明细】查找批次号
     *
     * @param sealDtlId
     * @return
     */
    public Lotno getLotnoBySealDtlId(Integer sealDtlId) {
        try {
            // 封库或解封单明细
            SealDtl sealDtl = sealDtlMapper.selectById(sealDtlId);

            // 封库或解封单主档
            SealHeader sealHeader = sealHeaderMapper.selectById(sealDtl.getSealHeaderId());

            Integer skuId = sealDtl.getSkuId();
            Integer owner = sealHeader.getOwner();
        	List<LotAttrDtl> lotAttrDtls = selectLotAttrDtlBySkuId(skuId);
        	Integer lotAttrId = lotAttrDtls.get(0).getLotAttrId();
        	
            // 入参DTO
            CreateLotnoDto dto = new CreateLotnoDto();
            Class<? extends CreateLotnoDto> dtoClass = dto.getClass();

            // 封库或解封单明细CLASS
            Class<? extends SealDtl> sealDtlClass = sealDtl.getClass();

            // 属性MAP（属性名称）
            Map<String, Object> lotAttr = new LinkedHashMap<String, Object>(lotAttrDtls.size());
            // 属性MAP（属性编码）
            Map<String, Object> lotAttrCode = new LinkedHashMap<String, Object>(lotAttrDtls.size());

            // 关键属性位置
            Set<Integer> keyAttrSeqSet = new HashSet<>();
            // 非关键属性位置
            Set<Integer> noKeyAttrSeqSet = new HashSet<>();
            
            // 遍历设置属性明细
            for (LotAttrDtl dtl : lotAttrDtls) {
                // 转换后的数值
                String lotAttrValue = "";

                String fieldName = null;
                // 转大写
                String attrCode = dtl.getAttrCode().toUpperCase();
                // 判断是不是封库或解封单明细表的字段
                if (systemMapper.existsColumn(WMS_SEAL_DTL, attrCode)) {
                    fieldName = attrCode;
                } else {
                	SealLotAttrDtlEnum sealLotAttrDtlEnum = SealLotAttrDtlEnum.getEnumByCode(attrCode);
                    if (sealLotAttrDtlEnum != null) {
                        fieldName = sealLotAttrDtlEnum.getEnumName();
                    }
                }
                if (fieldName != null) {
                    // 转驼峰格式
                    fieldName = StringUtils.strTransformation(fieldName);

                    // 属性code转换驼峰格式，获取货品清单对应字段的数值
                    Field sealDtlField = sealDtlClass.getDeclaredField(fieldName);
                    sealDtlField.setAccessible(true);

                    // 转换数值格式
                    Object fieldValue = sealDtlField.get(sealDtl);    // 封库或解封单明细数值
                    if (fieldValue != null && StringUtils.isNotNull(fieldValue.toString())) {
                        if (fieldValue instanceof Date) {
                            lotAttrValue = FORMAT.format(fieldValue);
                        } else {
                            lotAttrValue = fieldValue.toString();
                        }

                        // 获取入参dto对应属性的set函数，进行填值
                        Method dtoMethod = dtoClass.getMethod("setLotAttr" + dtl.getAttrSeq(), String.class);
                        dtoMethod.setAccessible(true);
                        dtoMethod.invoke(dto, lotAttrValue);
                    }
                }
                // PUT属性JSON（key:value）
                lotAttr.put(dtl.getAttr(), lotAttrValue);
                lotAttrCode.put(dtl.getAttrCode(), lotAttrValue);

                // 判断是否关键属性
                if(Objects.equals(BaseIsKeyEnum.YES.code, dtl.getIsKeyAttr())) {
                	keyAttrSeqSet.add(dtl.getAttrSeq());
            	} else {
            		noKeyAttrSeqSet.add(dtl.getAttrSeq());
            	}
            }
            // 以json格式，key:value形式存储批次属性1-12的内容
            dto.setLotAttr(JSON.toJSONString(lotAttr));
            dto.setLotAttrCode(JSON.toJSONString(lotAttrCode));

            // 批次属性ID
            dto.setLotAttrId(lotAttrId);
            // 货品
            dto.setSkuId(skuId);
            // 货主
            dto.setCustomerId(owner);
            return createLotno(dto, keyAttrSeqSet, noKeyAttrSeqSet, lotAttrDtls, NOT_UPDATE_LOT_NOKEYATTR);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 生成批次号
     * 
     * @param dto
     * @param keyAttrSeqSet		关键属性位置
     * @param noKeyAttrSeqSet	非关键属性位置
     * @return
     * @throws Exception
     */
	@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public Lotno createLotno(CreateLotnoDto dto, 
    		Set<Integer> keyAttrSeqSet, Set<Integer> noKeyAttrSeqSet, 
    		List<LotAttrDtl> lotAttrDtls, boolean isUpdateLotNokeyattr) throws Exception {
    	String lockKey = LotnoUtils.createRedisLockKeyOnlyKeyAttr(dto, keyAttrSeqSet);
    	RLock lock = redisson.getLock(lockKey);
        try {
        	// 加锁
        	long waitTime = LockNumber.SECONDS_I;	// 等待时间
        	long leaseTime = LockNumber.SECONDS_II;	// 过期时间
        	if (lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS)) {
        		// 默认货主
                if (CUSTOMER_ID == null) {
                    DataItemDetailDTO dataItem = dataItemDetailApi.getDataItemDetailByPCodeAndName(DEFAULT_OWNER, DEFAULT_OWNER_NAME);
                    CUSTOMER_ID = (dataItem == null || dataItem.getItemValue() == null ? 0 : Integer.parseInt(dataItem.getItemValue()));
                }

                // 当前系统时间
                Date date = new Date();
                
                // 定义批次属性实体
                Lotno lotno = null;
                
                // 《1》根据关键属性和非关键属性一起查询【非关键等值】
                QueryWrapper<Lotno> queryWrapper = LotnoUtils.queryWrapperLotAttr(dto, keyAttrSeqSet, true);
                List<Lotno> lotnoList = lotnoMapper.selectList(queryWrapper);
                if(lotnoList.size() > 0) {
                	lotno = lotnoList.get(0);
                }
                
                // 《2》根据关键属性和非关键属性一起查询【非关键模糊】
                if (lotno == null) {
                	queryWrapper = LotnoUtils.queryWrapperLotAttr(dto, keyAttrSeqSet, false);
                	lotnoList = lotnoMapper.selectList(queryWrapper);
                	if(lotnoList.size() > 0) {
                		lotno = lotnoList.get(0);
                	}
                }
                
                // 《3》只根据关键属性查询，因为旧数据的影响，可能会出现多条数据
                if (lotno == null) {
                	queryWrapper = LotnoUtils.queryWrapperLotAttrOnlyKeyAttr(dto, keyAttrSeqSet);
                	lotnoList = lotnoMapper.selectList(queryWrapper);
                	if (lotnoList.size() > 0) {
                		lotno = lotnoList.get(0);
                	}
                }
                
                // 《4》批次号找到，覆盖更新非关键属性
                if(lotno != null) {
                	// 是否需要更新批次号非关键属性标识
            		if(isUpdateLotNokeyattr) {
            			Class<?> lotnoClassName = Lotno.class;
            			Class<?> dtoClassName = CreateLotnoDto.class;
            			// 循环判断每个批次号的每个非关键批次属性值
            			for (Integer noKeyAttrSeq : noKeyAttrSeqSet) {
            				// 入参属性值
            				Field dtoField = dtoClassName.getDeclaredField("lotAttr" + noKeyAttrSeq);
            				dtoField.setAccessible(true);
            				Object dtoValue = dtoField.get(dto);
            				
            				// 新的批次属性值
            				if (dtoValue != null && StringUtils.isNotNull(dtoValue.toString())) {
            					// 将新的批次属性值 设置到批次属性字段
            					Method dtoMethod = lotnoClassName.getMethod("setLotAttr" + noKeyAttrSeq, String.class);
            					dtoMethod.setAccessible(true);
            					dtoMethod.invoke(lotno, dtoValue.toString());
        					}
            			}
            			
            			// 重新构造批次属性json-code
            			Map<String, Object> lotAttr = new LinkedHashMap<String, Object>(lotAttrDtls.size());
            			Map<String, Object> lotAttrCode = new LinkedHashMap<String, Object>(lotAttrDtls.size());
            			for (LotAttrDtl dtl : lotAttrDtls) {
            				Field field = lotnoClassName.getDeclaredField("lotAttr" + dtl.getAttrSeq());
            				field.setAccessible(true);
            				Object fieldValue = field.get(lotno);
            				
            				lotAttr.put(dtl.getAttr(), fieldValue);
            				lotAttrCode.put(dtl.getAttrCode(), fieldValue);
            			}
            			
            			lotno.setLotAttr(JSON.toJSONString(lotAttr));
            			lotno.setLotAttrCode(JSON.toJSONString(lotAttrCode));
            			lotno.setModifyUser(LOGIN_NAME);
            			lotno.setModifyTime(date);
            			lotnoMapper.updateData(lotno);
            			log.info("更新批次号：" + lotno.getLotno());
            		}
                } else {
                	// 《5》批次号未找到，根据属性创建
                	
            		// 判断货主是否为空，赋予默认货主
            		if (dto.getCustomerId() == null) {
            			dto.setCustomerId(CUSTOMER_ID);
            		}
            		
            		// 生成批次号【货主、货品、日期、'0001'】
            		String prefix = dto.getCustomerId() + dto.getSkuId() + FORMAT.format(date) + "0001";
            		List<String> lotnos = getSerialNumberApi.getSerialNumber("WMS_LOTNO", prefix, 6, 1, true, "10");
            		
            		lotno = new Lotno();
            		BeanUtil.copyNonNull(dto, lotno);
            		lotno.setLotno(lotnos.get(0));
            		lotno.setCreateUser(LOGIN_NAME);
            		lotno.setCreateTime(date);
            		lotnoMapper.insert(lotno);
            		log.info("创建新的批次号：" + lotnos.get(0));
            	}
                return lotno;
        	} else {
        		log.info("批次号加锁失败：" + lockKey);
        		return null;
        	}
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
			// 当前线程获取到锁再释放锁
            if (lock != null && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
		}
    }
}